﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Expect.Core.Errors;
using Expect.Core.Interfaces;

namespace Expect.Core.Helpers
{    
    /// <summary>
    /// Methods for fluent fixture resolution
    /// </summary>
    public static class ResolveFixtureAsFluentHelpers
    {
        /// <summary>
        /// Run the row as a fluent fixture
        /// </summary>
        /// <param name="context">execution context</param>
        /// <param name="fixtureDef">fixture definition</param>
        /// <param name="rw">row definition</param>
        /// <returns></returns>
        public static bool? AsFluentFixture(this IExecutionContext context, FixtureDefinition fixtureDef, FixtureRow rw)
        {
            var spec = fixtureDef.Parent;

            // Try to bubble the specifications hierarchy to find a match
            while (spec != null)
            {
                if (spec.Implementation != null)
                {
                    bool? res = RunAsFluentFixture(context, fixtureDef, rw, spec.Implementation);
                    if (res.HasValue) return res;
                }
                else
                {
                    string classname = spec.ClassName ?? spec.Name;
                    if (classname != spec.Id)
                    {
                        var err = ElementNotFoundError.Class(spec.ClassName ?? spec.Name, ElementKind.Specification);
                        fixtureDef.Status.AddError(err);
                    }
                }
                // string ownername = (spec.Implementation != null) ? spec.Implementation.GetType().Name : spec.Title;
                // var err = ElementNotFoundError.Method(rw[0].Text, ownername);
                // rw.Status.AddError(err);  
                spec = spec.ParentSpecification;
            }

            // Try to find a match in test execution container
            if (context.Container != null)
            {
                bool? res = RunAsFluentFixture(context, fixtureDef, rw, context.Container);
                if (res.HasValue) return res;

                //var err = ElementNotFoundError.Method(rw[0].Text, Container.GetType().Name);
                //rw.Status.AddError(err);  
            }

            return null;
        }

        /// <summary>
        /// Run the row as a fluent fixture
        /// </summary>
        /// <param name="context">execution context</param>
        /// <param name="FixtureDef">fixture definition</param>
        /// <param name="rw">row definition</param>
        /// <param name="owner">owner of method</param>
        /// <returns></returns>
        public static bool? RunAsFluentFixture(this IExecutionContext context, FixtureDefinition FixtureDef, FixtureRow rw, object owner)
        {
            Type ownerType = owner.GetType();
            if (owner == null)
            {
                throw new ArgumentNullException("Target type must not be null");
            }

            string conformed = context.NameProvider.ElementName(rw[0].Text);
            if (context.HasMethod(owner.GetType(), conformed, 1))
            {
                if (context.ResolveOnly)
                    return true;

                return ResolutionHelpers.RunRowMethod(rw, conformed, ownerType.FullName, 1, () => RunFluentTestFixtureRow(context, owner, rw));
            }
            rw.Status.AddError(ElementNotFoundError.Method(conformed, ownerType.FullName, 1));

            IEnumerable<string> names = rw.NamesForFluent();
            IEnumerable<string> args = rw.ArgsForFluent();
            int numArgs = args.Count();

            conformed = context.NameProvider.ElementName(names.ToArray());
            if (!ReflectionHelper.HasMethod(context, ownerType, conformed, numArgs))
            {
                rw.Status.AddError(ElementNotFoundError.Method(conformed, ownerType.FullName, numArgs));
                rw.Status.Success = null;
                return null;
            }

            if (context.ResolveOnly) 
                return true;

            return ResolutionHelpers.RunRowMethod(rw, conformed, ownerType.FullName, numArgs, () => ReflectionHelper.RunMethod(context, owner, rw[0], rw[0], conformed, false, args.ToArray()));
        }

        private static bool? RunFluentTestFixtureRow(IExecutionContext Context, object owner, FixtureRow rw)
        {
            Type ownerType = owner.GetType();
            for (int i = 0; i < rw.Count; i++)
            {
                string conformed = Context.NameProvider.ElementName(rw[i].Text);
                string arg = null;
                try
                {
                    if (i < rw.Count - 1)
                    {
                        arg = rw[i + 1].Text;
                        if (!Context.HasMethod(owner.GetType(), conformed, 1))
                        {
                            rw.Status.AddError(ElementNotFoundError.Method(conformed, ownerType.FullName, 1));
                            return null;
                        }
                        owner = Context.RunMethod(owner, conformed, new string[] { arg });
                        rw[i].Status.Success = true;
                        i++;
                    }
                    else
                    {
                        if (owner != null && Context.HasMethod(ownerType, conformed, 0))
                        {
                            owner = Context.RunMethod(owner, conformed, null);
                        }
                        rw[i].Status.Success = true;
                    }

                }
                catch (Exception)
                {
                    rw[i].Status.Success = false;
                    throw;
                }
            }

            return true;
        }

    }
}
